/// 库存模块
module factories.storage;

import factories.building;
import factories.common;
import factories.generated;
import factories.procs;
import factories.util;

/// 操作状态
union OpStatus {
	struct {
		ubyte status;
		ubyte detail;
		short slot;
	}

	uint value;
	this(Status s, ubyte d = 0) {
		status = cast(ubyte)s;
		detail = d;
	}
}

/// 仓库
struct Storage {
	/// 物品容量：capacities[i]表示贮藏方式为i的物品的剩余容量
	uint[StorageType.max + 1] capacities;
	/// 物品数量
	uint[Items.max + 1] count;
	/// 更新容量
	void updateCapacities(uint flags, uint level) {
		foreach (t; StorageType.min .. StorageType.max + 1) {
			if (flags >> t & 1)
				capacities[t] = (100 - t * 10) << level;
		}
		foreach (Items i, c; count) {
			if (c) {
				const t = storageType(i);
				if (c > capacities[t])
					capacities[t] = 0;
				else
					capacities[t] -= c;
			}
		}
	}
}

/// 仓库列表
@persist() Map!(int, Storage, maxStorages) storages;

/// 初始化仓库
void initStorage() {
	import core.stdc.string;

	/// 默认容量
	enum uint[StorageType.max + 1] defaultCapacity = [10];

	// 全局仓库
	storages[-1] = Storage(defaultCapacity);
	memset(proc.ptr, 255, proc.sizeof);
}

/// 获取仓库类型
@property uint storageFlags(Buildings b) {
	switch (b) with (Buildings) {
	case warehouse:
		return 1 << StorageType.solid | 1 << StorageType.liquid | 1 << StorageType.radioactive;
	case battery:
		return 1 << StorageType.electricity;
	case pool, reservoir:
		return 1 << StorageType.liquid;
	case tank:
		return 1 << StorageType.liquid | 1 << StorageType.gas;
	default:
	}
	return 0;
}

unittest {
	assert(storageFlags(Buildings.warehouse));
	assert(storageFlags(Buildings.battery));
	assert(storageFlags(Buildings.pool));
	assert(storageFlags(Buildings.tank));
}

/// 获取仓库，slot为-1表示全局仓库
extern (C) export Storage* getStorage(int slot) {
	return slot in storages;
}

unittest {
	initStorage();
	assert(getStorage(-1));
	storages[65] = Storage();
	assert(storages.length == 2);
	assert(getStorage(65));
}

/// 获取物品的贮藏方式
extern (C) export StorageType storageType(Items id) {
	import factories.game;

	return cast(StorageType)constants[0][id].b;
}

/// 物品变化
struct ItemDelta {
	/// 物品ID
	Items id;
	/// 数量，原料数量为负数，产品数量为正数
	int count;
}

/// 配方
struct Recipe {
	ItemDelta[] deltas;

	alias deltas this;
}

/// 添加物品
extern (C) export Status addItem(Items id, int count, int slot) {
	auto storage = getStorage(slot);
	if (!storage)
		return Status.storageNotExist;

	if (count < 0) {
		if (storage.count[id] < -count)
			return Status.lack;
	} else if (storage.capacities[storageType(id)] < count)
		return Status.storageFull;

	storage.count[id] += count;
	storage.capacities[storageType(id)] -= count;
	return Status.ok;
}

/// 尝试应用变化
extern (C) export uint tryApply(int slot, uint procId, int k) {
	import factories.factory;

	if (procId >= procs.length)
		return Status.recipeNotExist;

	const p = Process(procId, cast(short)k, 0, 1);
	Storage*[] stores;
	Storage*[1] s;
	if (slot >= 0) {
		const id = net.id[slot];
		auto netId = id in net.netIds;
		if (!netId)
			return Status.storageNotExist;
		auto nodes = net[*netId];
		stores = cast(Storage*[])nodes[0 .. nodes.bound];
	} else {
		s[0] = getStorage(-1);
		stores = s;
	}
	return tryProduce(&p, stores, 0, p.k).value;
}
/+
/// 尝试生产
OpStatus tryProduce(ref Storage storage, in Process* p) {
	const deltas = procs[p.recipe];
	foreach (d; deltas) {
		const dt = d.count * p.k;
		// 检查是否有足够的物品
		if (dt < 0) {
			if (storage.count[d.id] < uint(-dt))
				return OpStatus(Status.lack, cast(ubyte)d.id);
		} else // 检查是否有足够的空间
			if (dt > storage.capacities[d.id.storageType])
				return OpStatus(Status.storageFull, cast(ubyte)d.id.storageType);
	}
	// 应用变化
	foreach (d; deltas) {
		const count = d.count * p.k;
		storage.count[d.id] += count;
		storage.capacities[d.id.storageType] -= count;
	}
	return OpStatus();
}
+/

/// produce为假表示检查是否可以生产，produce为真表示生产
OpStatus tryProduce(in Process* p, Storage*[] storages,
	uint tick, int k, bool produce = true) {
	import factories.map;

	if (produce) {
		const status = tryProduce(p, storages, tick, k, false);
		if (status.status) {
			return status;
		}
	}

	if (p.period == 0)
		return OpStatus(Status.paused);

	if ((p.step + tick) % p.period != 0)
		return OpStatus(Status.inProgress);

	foreach (d; procs[p.recipe]) {
		int c = d.count * k;
		if (c < 0) {
			c = -c;
			foreach (s; storages) {
				auto count = &s.count[d.id];
				auto cap = &s.capacities[d.id.storageType];
				if (*count >= c) {
					if (produce)
						*count -= c, *cap += c;
					c = 0;
				} else {
					c -= *count;
					if (produce) {
						*cap += *count;
						*count = 0;
					}

				}
				if (c == 0)
					break;
			}
			if (c > 0) {
				return OpStatus(Status.lack, cast(ubyte)d.id);
			}
		} else {
			foreach (s; storages) {
				auto cap = &s.capacities[d.id.storageType];
				if (*cap >= c) {
					if (produce) {
						s.count[d.id] += c;
						*cap -= c;
					}
					c = 0;
					break;
				}
				c -= *cap;
				if (produce) {
					s.count[d.id] += *cap;
					*cap = 0;
				}
			}
			if (c) {
				return OpStatus(Status.storageFull, cast(ubyte)d.id);
			}
		}
	}
	return OpStatus();
}
